﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.UI;
using Ext.Net.Utilities;

namespace Ext.Net
{
    public class XControlResponse
    {
        public ResourceManager ResourceManager { get; protected set; }
        public SelfRenderingPage Page { get; protected set; }
        public readonly ParameterCollection Params = new ParameterCollection();
        /// <summary>
        /// на клиенте выполнять скрипты, добавлненные на сервере, до обработчика события или после
        /// </summary>
        public bool ExecScriptAfterClient;
        public bool SafeExecScript;

        public XControlResponse()
        {
            Page = new SelfRenderingPage();
            ResourceManager = new ResourceManager
            {
                RenderScripts = ResourceLocationType.None,
                RenderStyles = ResourceLocationType.None,
            };
            Page.Controls.Add(ResourceManager);

            Page.Items[typeof(ResourceManager)] = ResourceManager;
            HttpContext.Current.Items[typeof(ResourceManager)] = ResourceManager;
        }

        protected class LoaderControl
        {
            public Control Control;
            public XControl[] ScriptControls;
            public bool Creating;
            public RenderMode RenderMode;
            public string RenderElement;
        }

        protected readonly List<LoaderControl> Controls = new List<LoaderControl>();

        protected static void SetResources(Control seed)
        {
            if (seed.Visible && seed is XControl)
                ((XControl)seed).SetResources();

            foreach (Control control in seed.Controls)
            {
                SetResources(control);
            }
        }

        protected Control Load(string path, string id, bool creating)
        {
            if (id.IsEmpty())
                throw new ArgumentNullException("id");

            var uc = Page.LoadControl(path);
            uc.ID = id;

            if (!creating && uc is UserControl)
            {
                //For each child XControls set .AllowCallbackScriptMonitoring to true
                ((UserControl)uc).DesignerInitialize();
            }

            return uc;
        }

        protected T Load<T>(string id, bool creating)
            where T : Control
        {
            if (id.IsEmpty())
                throw new ArgumentNullException("id");

            var uc = Page.LoadControl(typeof(T), null);
            uc.ID = id;

            if (!creating)
                if (uc is UserControl)
                {
                    //For each child XControls set .AllowCallbackScriptMonitoring to true
                    ((UserControl)uc).DesignerInitialize();
                }
                else if (uc is ContainerBase)
                    ((ContainerBase)uc).StartModify();

            return (T)uc;
        }


        #region Create

        public TControl Create<TControl>(string id,
            RenderMode renderMode = RenderMode.RenderTo, string renderElement = "form1")
            where TControl : Control
        {
            var uc = Load<TControl>(id, true);
            Controls.Add(new LoaderControl
            {
                Control = uc,
                Creating = true,
                RenderMode = renderMode,
                RenderElement = renderElement,
            });
            return uc;
        }

        public Control Create(string path, string id,
            RenderMode renderMode = RenderMode.RenderTo, string renderElement = "form1")
        {
            var uc = Load(path, id, true);
            Controls.Add(new LoaderControl
            {
                Control = uc,
                Creating = true,
                RenderMode = renderMode,
                RenderElement = renderElement,
            });
            return uc;
        }
        public TControl Create<TControl>(string path, string id,
            RenderMode renderMode = RenderMode.RenderTo, string renderElement = "form1")
            where TControl : Control
        {
            var uc = Load(path, id, true);
            Controls.Add(new LoaderControl
            {
                Control = uc,
                Creating = true,
                RenderMode = renderMode,
                RenderElement = renderElement,
            });
            return (TControl)uc;
        }

        public Control Create(string path, string id, Func<Control, XControl[]> getScriptControl,
            RenderMode renderMode = RenderMode.RenderTo, string renderElement = "form1")
        {
            if (getScriptControl == null)
                throw new ArgumentNullException("getScriptControl");

            var uc = Load(path, id, true);
            Controls.Add(new LoaderControl
            {
                Control = uc,
                Creating = true,
                ScriptControls = getScriptControl(uc),
                RenderMode = renderMode,
                RenderElement = renderElement,
            });

            return uc;
        }

        public Control Create(string path, string id, string scriptControlID,
            RenderMode renderMode = RenderMode.RenderTo, string renderElement = "form1")
        {
            if (scriptControlID == null)
                throw new ArgumentNullException("scriptControlID");

            return Create(path, id, c => new[] {(XControl)c.FindControl(scriptControlID)}, renderMode, renderElement);
        }

        public TControl Create<TControl>(string path, string id, Func<TControl, XControl[]> getScriptControl,
            RenderMode renderMode = RenderMode.RenderTo, string renderElement = "form1")
            where TControl : Control
        {
            if (getScriptControl == null)
                throw new ArgumentNullException("getScriptControl");

            return (TControl)Create(path, id, c => getScriptControl((TControl)c), renderMode, renderElement);
        }

        public TControl Create<TControl>(string path, string id, string scriptControlID,
            RenderMode renderMode = RenderMode.RenderTo, string renderElement = "form1")
            where TControl : Control
        {
            if (scriptControlID == null)
                throw new ArgumentNullException("scriptControlID");

            return (TControl)Create(path, id, c => new[] { (XControl)c.FindControl(scriptControlID) }, renderMode, renderElement);
        }

        #endregion


        #region Modify

        public TControl Modify<TControl>(string id)
            where TControl : Control
        {
            var uc = Load<TControl>(id, false);
            Controls.Add(new LoaderControl { Control = uc, Creating = false, });
            return uc;
        }


        public Control Modify(string path, string id)
        {
            var uc = Load(path, id, false);
            Controls.Add(new LoaderControl { Control = uc, Creating = false, });
            return uc;
        }
        public TControl Modify<TControl>(string path, string id)
            where TControl : Control
        {
            return (TControl)Modify(path, id);
        }

        public Control Modify(string path, string id, Func<Control, XControl[]> getScriptControl)
        {
            if (getScriptControl == null)
                throw new ArgumentNullException("getScriptControl");

            var uc = Load(path, id, false);
            Controls.Add(new LoaderControl
            {
                Control = uc,
                Creating = false,
                ScriptControls = getScriptControl(uc),
            });

            return uc;
        }

        public Control Modify(string path, string id, string scriptControlID)
        {
            if (scriptControlID == null)
                throw new ArgumentNullException("scriptControlID");

            return Modify(path, id, c => new[]{(XControl)c.FindControl(scriptControlID)});
        }

        public TControl Modify<TControl>(string path, string id, Func<TControl, XControl[]> getScriptControl)
            where TControl : Control
        {
            if (getScriptControl == null)
                throw new ArgumentNullException("getScriptControl");

            return (TControl)Modify(path, id, c => getScriptControl((TControl)c));
        }

        public TControl Modify<TControl>(string path, string id, string scriptControlID)
            where TControl : Control
        {
            if (scriptControlID == null)
                throw new ArgumentNullException("scriptControlID");

            return (TControl)Modify(path, id, c => new[] { (XControl)c.FindControl(scriptControlID) });
        }

        #endregion


        #region Render

        public void Render()
        {
            if (ResourceManager == null) return;

            foreach (var c in Controls)
            {
                if (c.Creating)
                    if (c.ScriptControls != null)
                        foreach (var sc in c.ScriptControls)
                        {
                            sc.Render(c.RenderElement, c.RenderMode);   
                        }
                    else
                        foreach (var sc in c.Control.Controls.OfType<XControl>())
                        {
                            sc.Render(
                                sc is Window ? "form1" : c.RenderElement,
                                sc is Component ? c.RenderMode : RenderMode.Auto);
                        }
            }
        }

        public static Control Render(string path, string id,
            RenderMode renderMode = RenderMode.RenderTo, string renderElement = "form1")
        {
            var loader = new XControlResponse();
            var c = loader.Create(path, id, renderMode, renderElement);
            loader.Render();
            return c;
        }

        public static Control Render(string path, string id, Func<Control, XControl[]> getScriptControl,
            RenderMode renderMode = RenderMode.RenderTo, string renderElement = "form1")
        {
            var loader = new XControlResponse();
            var c = loader.Create(path, id, getScriptControl, renderMode, renderElement);
            loader.Render();
            return c;
        }

        public static void Render(string path, string id, string scriptControlID,
            RenderMode renderMode = RenderMode.RenderTo, string renderElement = "form1")
        {
            var loader = new XControlResponse();
            loader.Create(path, id, scriptControlID, renderMode, renderElement);
            loader.Render();
        }

        public static void Render<TControl>(string path, string id, Func<TControl, XControl[]> getScriptControl,
            RenderMode renderMode = RenderMode.RenderTo, string renderElement = "form1")
            where TControl : Control
        {
            var loader = new XControlResponse();
            loader.Create(path, id, getScriptControl, renderMode, renderElement);
            loader.Render();
        }

        #endregion


        #region Script

        public void AddScript(string script)
        {
            if (ResourceManager != null)
                ResourceManager.RegisterOnReadyScript(script);
        }

        public string ToScript()
        {
            if (ResourceManager == null) return null;

            var sb = new StringBuilder();

            if (ExecScriptAfterClient)
                sb.AppendLine("var f = function() {");
            if (SafeExecScript)
                sb.AppendLine("try {");


            foreach (var c in Controls)
            {
                if (c.Creating)
                    if (c.ScriptControls != null)
                        foreach (var sc in c.ScriptControls)
                        {
                            sb.AppendLine(sc.ToScript(c.RenderMode, c.RenderElement));
                        }
                    else
                        foreach (var sc in c.Control.Controls.OfType<XControl>())
                        {
                            sb.AppendLine(sc.ToScript(c.RenderMode, c.RenderElement));
                        }
                else
                    if (c.ScriptControls != null)
                        foreach (var sc in c.ScriptControls)
                        {
                            SetResources(sc);
                        }
                    else if (c.Control is UserControl)
                        foreach (var sc in c.Control.Controls.OfType<XControl>())
                        {
                            SetResources(sc);
                        }
                    else
                        SetResources(c.Control);
            }

            ResourceManager.SetResources();
            sb.AppendLine(ResourceManager.BuildScriptBlock(false));

            if (SafeExecScript)
                sb.AppendLine("} catch(__err) {}");

            if (ExecScriptAfterClient)
                sb.AppendLine("};\no.userSuccess = o.userSuccess ? o.userSuccess.createSequence(f) : f;");

            return sb.ToString();
        }

        public override string ToString()
        {
            return ToScript();
        }

        public static XControlResponse operator +(XControlResponse a, string b)
        {
            a.AddScript(b);
            return a;
        }

        public static implicit operator DirectResponse(XControlResponse loader)
        {
            return new DirectResponse(loader.ToScript()) { ExtraParamsResponse = loader.Params.ToJson() };
        }

        #endregion

    }
}